﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections;

namespace EnsureWeb.Share
{

    // Use tree structure to store the object, the flat structure is different from the Hashtable
    // XPath to get through to the object
	public class Cache {
        private XmlElement rootMap;				//	Dynamically build the Xml document root
		private ICacheStrategy cacheStrategy;
        public static readonly Cache Instance = new Cache();	// Implements the Singleton pattern
        private XmlDocument doc = new XmlDocument();	//Building Xml document

        // Private constructor, used to implement the Singleton pattern
		private Cache() {

            // Here the application of the Strategy pattern。
            // Improved: You can use the definition of what Strategy to the app.config, and then use reflection to dynamically create the type of
			cacheStrategy = new DefaultCacheStrategy();

            // Create a document root, used to map the actual data storage (such as Hashtable) and Xml document
			rootMap = doc.CreateElement("Cache");

            // Add the root node
			doc.AppendChild(rootMap);
		}

        // Access to objects based on the XPath
        // First obtained under the Xpath object Key, and then get the actual object based on Key
		public virtual object GetItem(string xpath) {

			object obj = null;
			xpath = PrepareXPath(xpath);
			XmlNode node = rootMap.SelectSingleNode(xpath);

			if (node != null) {
                // Key to retrieve the object
				string key = node.Attributes["key"].Value;

                //Get the actual object
				obj = cacheStrategy.GetItem(key);
			}
			return obj;
		}


        // Get a group of objects, this time as a group node xpath
		public virtual object[] GetList(string xpath) {
			xpath = PrepareXPath(xpath);

			XmlNode group = rootMap.SelectSingleNode(xpath);

            // Get all the sub-node under the node (using the [@ key] to ensure that the child node must contain the key attribute)
			XmlNodeList results = group.SelectNodes(xpath + "/*[@key]");

			ArrayList objects = new ArrayList();

			string key;

			foreach (XmlNode result in results) {
				key = result.Attributes["key"].Value;
				Object obj = cacheStrategy.GetItem(key);
				objects.Add(obj);
			}

			return (object[])objects.ToArray(typeof(object));
		}


		// Add the object, the object is still the Add to ICacheStrategy designated storage location,
        // Dynamically created Xml node only save the object of Id (key), used to map the relationship between
		public virtual void AddItem(string xpath, object obj) {

            // Get Xpath, such as /Cache/BookStore/Book/Title
			string newXpath = PrepareXPath(xpath);
	
			int separator = newXpath.LastIndexOf("/");

            // Get the stacking order of group nodes, such as /Cache/BookStore/Book
			string group = newXpath.Substring(0, separator);

            // For leaf node name, such as Title
			string element = newXpath.Substring(separator + 1);

            // Access to group nodes
			XmlNode groupNode = rootMap.SelectSingleNode(group);

            // If the group node does not exist, create it
			if (groupNode == null) {
				lock (this) {
					groupNode = CreateNode(group);
				}
			}

            // Create a unique key, used to map the Xml and object's primary key
			string key = Guid.NewGuid().ToString();

            // Create a new node
			XmlElement objectElement = rootMap.OwnerDocument.CreateElement(element);

            // Create a node property key
			XmlAttribute objectAttribute = rootMap.OwnerDocument.CreateAttribute("key");

            // Just set the property value generated Guid
			objectAttribute.Value = key;

            // Add the attribute to the node
			objectElement.Attributes.Append(objectAttribute);

            // Add to groupNode the node below (groupNode Xpath level for the part)
			groupNode.AppendChild(objectElement);

            // Add the key and object to the actual storage location, such as Hashtable
			cacheStrategy.AddItem(key, obj);
		}


        // Delete the object based on the XPath
		public virtual void RemoveItem(string xpath) {

			xpath = PrepareXPath(xpath);
			XmlNode result = rootMap.SelectSingleNode(xpath);

            string key;			//  Object Id 

            // If the result is a set of nodes (nodes with children)
			if (result.HasChildNodes) {

                // Select all the key attribute that contains the node
				XmlNodeList nodeList = result.SelectNodes("descendant::*[@key]");
				
				foreach (XmlNode node in nodeList) {

					key = node.Attributes["key"].Value;

                    // Removed from the Xml document node
					node.ParentNode.RemoveChild(node);

                    // Removed from the actual storage node
					cacheStrategy.RemoveItem(key);
				}
            }
            else
            {		// If the result is a leaf node (without child nodes)

				key = result.Attributes["key"].Value;
				result.ParentNode.RemoveChild(result);
				cacheStrategy.RemoveItem(key);
			}
		}


        // Create a node based on the XPath
		private XmlNode CreateNode(string xpath) {

			string[] xpathArray = xpath.Split('/');
			string nodePath = "";

            // Parent node initialization
			XmlNode parentNode = (XmlNode)rootMap;	

            // Drill down XPath at all levels, if the node does not exist, create
            // Example /DvdStore/Dvd/NoOneLivesForever
			for (int i = 1; i < xpathArray.Length; i++) {
				XmlNode node = rootMap.SelectSingleNode(nodePath + "/" + xpathArray[i]);

				if (node == null) {
                    XmlElement newElement = rootMap.OwnerDocument.CreateElement(xpathArray[i]);	// Create a node
					parentNode.AppendChild(newElement);
				}

                // Create a new path, update the parent node to the next level
				nodePath = nodePath + "/" + xpathArray[i];
				parentNode = rootMap.SelectSingleNode(nodePath);
			}

			return parentNode;
		}

        // Build XPath, to make it / Cache the root node, and clear than the "/" character
		private string PrepareXPath(string xpath) {
			string[] xpathArray = xpath.Split('/');
            xpath = "/Cache";		// Here's Name to be created with the constructor corresponds to the root of Name
			foreach (string s in xpathArray) {
				if (s != "") {
					xpath += "/" + s;
				}
			}
			return xpath;
		}

	}
}
